
#pragma once

#include "Win32RenderWindow.h"
#include "SystemMessage.h"
#include <assert.h>
#include <Windows.h>
#include "EngineCommand.h"

namespace FlameEngine
{
	Win32RenderWindow* Win32RenderWindow::g_pInstance = nullptr;

	LRESULT CALLBACK Win32RenderWindow::StaticWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
	{
		if (Win32RenderWindow::g_pInstance)
		{
			return g_pInstance->WndProc(hWnd, uMsg, wParam, lParam);
		}
		return DefWindowProc(hWnd, uMsg, wParam, lParam);
	}

	Win32RenderWindow::Win32RenderWindow(HINSTANCE hInstance)
		:m_hInstance(hInstance),
		m_wndClientSize(800,600),
		m_wndPosition(0,0),
		m_wndHandle(0),
		m_title("Flame Game"),
		m_appName("Flame Engine"),
		m_wndStyle(wsf_default),
		m_wndState(ws_normal),
		m_allowUserResize(true),
		m_isDraggingWnd(false)
	{
		if(g_pInstance == nullptr)
			g_pInstance = this;
	}

	Win32RenderWindow::~Win32RenderWindow()
	{

	}

	void Win32RenderWindow::CreateAndRun(CreateWndCmd* pWndCmd)
	{
		if(pWndCmd)
		{
			m_wndStyle = pWndCmd->style;
			m_wndPosition.x = pWndCmd->posX;
			m_wndPosition.y = pWndCmd->posY;
			m_wndClientSize.x = pWndCmd->clientWidth;
			m_wndClientSize.y = pWndCmd->clientHeight;
			m_minClientWndSize.x = pWndCmd->minClientWidth;
			m_minClientWndSize.y = pWndCmd->minClientHeight;
			m_allowUserResize = pWndCmd->allowUserResize;
			m_appName = pWndCmd->appName;
			m_title = pWndCmd->titleName;
		}

		WNDCLASSEX wndClass;
		wndClass.cbSize			= sizeof(WNDCLASSEX);
		wndClass.style			=  CS_HREDRAW | CS_VREDRAW;
		wndClass.lpfnWndProc	= Win32RenderWindow::StaticWndProc;
		wndClass.cbClsExtra		= 0;
		wndClass.cbWndExtra		= 0;
		wndClass.hInstance		= m_hInstance;
		wndClass.hIcon			= nullptr;
		wndClass.hCursor		= LoadCursor(NULL, IDC_ARROW);
		wndClass.hbrBackground	= (HBRUSH)GetStockObject(BLACK_BRUSH);
		wndClass.lpszMenuName	= nullptr;
		wndClass.lpszClassName	= m_appName.c_str();
		wndClass.hIconSm		= nullptr;

		if(!RegisterClassEx(&wndClass))
		{
			DWORD id = GetLastError();
			MessageBox(nullptr,"Failed to register game window!",nullptr,MB_ICONERROR);
			return;
		}

		uint32_t wndStyle = GetWin32WndStyle();

		if(m_minClientWndSize.x < 1080) m_minClientWndSize.x = 1080;
		if(m_minClientWndSize.y < 720) m_minClientWndSize.y = 720;
		if(m_wndClientSize.x < m_minClientWndSize.x) m_wndClientSize.x = m_minClientWndSize.x;
		if(m_wndClientSize.y < m_minClientWndSize.y) m_wndClientSize.y = m_minClientWndSize.y;

		int32_t screenWidth = GetSystemMetrics(SM_CXSCREEN);
		int32_t screenHeight = GetSystemMetrics(SM_CYSCREEN);

		if(m_wndClientSize.x > screenWidth) m_wndClientSize.x = screenWidth;
		if(m_wndClientSize.y > screenHeight) m_wndClientSize.y = screenHeight;

		RECT wndRect;
		SetRect(&wndRect,0,0,m_wndClientSize.x,m_wndClientSize.y);
		AdjustWindowRect(&wndRect,wndStyle,false);

		m_wndSize.x = wndRect.right;
		m_wndSize.y = wndRect.bottom;

		Int2 wndPos = m_wndPosition;
		if(m_wndPosition.x == 0)	
			wndPos.x = (screenWidth - m_wndSize.x) / 2;

		if(m_wndPosition.y == 0)	
			wndPos.y = (screenHeight - m_wndSize.y) / 2;
		
		
		m_wndHandle = ::CreateWindow(m_appName.c_str(),m_title.c_str(),wndStyle,
			wndPos.x,wndPos.y,m_wndSize.x,m_wndSize.y,nullptr,nullptr,m_hInstance,nullptr);

		if(m_wndHandle == 0)
		{
			MessageBox(nullptr,"Failed to create game window!",nullptr,MB_ICONERROR);
			return;
		}

		::ShowWindow(m_wndHandle, SW_SHOWDEFAULT);
		::UpdateWindow(m_wndHandle);
	}

	void Win32RenderWindow::Close()
	{
		assert(false);
	}

	void Win32RenderWindow::ReceiveWindowsMsg()
	{
		MSG msg;
		while(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
		{
			if(msg.message == WM_QUIT)
				break;

			TranslateMessage(&msg);
			DispatchMessage(&msg);
		}
	}

	LRESULT Win32RenderWindow::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
	{
		if(uMsg == WM_KEYDOWN || uMsg == WM_SYSKEYDOWN || uMsg == WM_KEYUP ||uMsg == WM_SYSKEYUP )
			ProcessKeyboardInput(hWnd,uMsg,wParam,lParam);

		if( uMsg == WM_LBUTTONDOWN ||uMsg == WM_LBUTTONUP ||
			uMsg == WM_MBUTTONDOWN ||uMsg == WM_MBUTTONUP ||
			uMsg == WM_RBUTTONDOWN ||uMsg == WM_RBUTTONUP ||
			uMsg == WM_XBUTTONDOWN ||uMsg == WM_XBUTTONUP ||
			uMsg == WM_MOUSEWHEEL  ||uMsg == WM_MOUSEMOVE)
		{
			ProcessMouseInput(hWnd,uMsg,wParam,lParam);
		}

		switch (uMsg)
		{
		case WM_CREATE:
		{
			if(m_systemMsgCallback)
				m_systemMsgCallback(sm_wndCreated);
		}
			break;
		case WM_SIZE:
			{
				if(wParam == SIZE_MINIMIZED)
				{
					if(m_systemMsgCallback)
						m_systemMsgCallback(sm_wndMinimized);
				}
				else
				{
					RECT clientRect;
					GetClientRect(hWnd, &clientRect);

					RECT wndRect;
					GetWindowRect(hWnd,&wndRect);

					if( clientRect.top == 0 && clientRect.bottom == 0 )
					{
						// Rapidly clicking the task bar to minimize and restore a window
						// can cause a WM_SIZE message with SIZE_RESTORED when 
						// the window has actually become minimized due to rapid change
						// so just ignore this message
					}
					else if(wParam == SIZE_MAXIMIZED)
					{
						m_wndClientSize.x = clientRect.right - clientRect.left;
						m_wndClientSize.y = clientRect.bottom - clientRect.top;
						m_wndSize.x = wndRect.right - wndRect.left;
						m_wndSize.y = wndRect.bottom - wndRect.top;

						if(m_systemMsgCallback)
							m_systemMsgCallback(sm_wndMaxmized);
					}
					else if(wParam == SIZE_RESTORED)
					{
						m_wndSize.x = wndRect.right - wndRect.left;
						m_wndSize.y = wndRect.bottom - wndRect.top;
						
						m_wndClientSize.x = clientRect.right - clientRect.left;
						m_wndClientSize.y = clientRect.bottom - clientRect.top;

						if (m_systemMsgCallback)
							m_systemMsgCallback(sm_wndResize);
					}
					else if(m_isDraggingWnd)
					{
						// If we're neither maximized nor minimized, the window size 
						// is changing by the user dragging the window edges.  In this 
						// case, we don't reset the device yet -- we wait until the 
						// user stops dragging, and a WM_EXITSIZEMOVE message comes.
					}
					else
					{
						//WM_SIZE come from resizing the window via API like SetWindowPos
						m_wndSize.x = wndRect.right - wndRect.left;
						m_wndSize.y = wndRect.bottom - wndRect.top;

						uint32_t clientWidth = clientRect.right - clientRect.left;
						uint32_t clientHeight = clientRect.bottom - clientRect.top;
						if(m_wndClientSize.x != clientWidth || m_wndClientSize.y != clientHeight)
						{
							m_wndClientSize.x = clientWidth;
							m_wndClientSize.y = clientHeight;

							if(m_systemMsgCallback)
								m_systemMsgCallback(sm_wndResize);
						}

						if(m_wndPosition.x != wndRect.left || m_wndPosition.y != wndRect.top)
						{
							m_wndPosition.x = wndRect.left;
							m_wndPosition.y = wndRect.top;
							if(m_systemMsgCallback)
								m_systemMsgCallback(sm_wndMoved);
						}
					}
				}
			}
			break;
		case WM_GETMINMAXINFO:
			{
				//set window min max size
				uint32_t wndStyle = GetWin32WndStyle(); 
				RECT minWndRect;
				SetRect(&minWndRect,0,0,m_minClientWndSize.x,m_minClientWndSize.y);
				AdjustWindowRect(&minWndRect,wndStyle,false);

				( ( MINMAXINFO* )lParam )->ptMinTrackSize.x = minWndRect.right - minWndRect.left;
				( ( MINMAXINFO* )lParam )->ptMinTrackSize.y = minWndRect.bottom - minWndRect.top;
			}
			break;
		case WM_ENTERSIZEMOVE:
			{
				m_isDraggingWnd = true;
				if(m_systemMsgCallback)
					m_systemMsgCallback(sm_wndBeginResizeMove);
			}
			break;
		case WM_EXITSIZEMOVE:
			{
				m_isDraggingWnd = false;
	
				bool wndSizeChanged = false;
				bool wndMoved = false;

				RECT clientRect,wndRect;
				GetClientRect(hWnd, &clientRect);
				GetWindowRect(hWnd,&wndRect);

				m_wndSize.x = wndRect.right - wndRect.left;
				m_wndSize.y = wndRect.bottom - wndRect.top;

				uint32_t clientWidth = clientRect.right - clientRect.left;
				uint32_t clientHeight = clientRect.bottom - clientRect.top;
				if(m_wndClientSize.x != clientWidth || m_wndClientSize.y != clientHeight)
				{
					wndSizeChanged = true;
					m_wndClientSize.x = clientWidth;
					m_wndClientSize.y = clientHeight;
				}

				if(m_wndPosition.x != wndRect.left || m_wndPosition.y != wndRect.top)
				{
					wndMoved = true;
					m_wndPosition.x = wndRect.left;
					m_wndPosition.y = wndRect.top;
				}

				if(m_systemMsgCallback)
				{
					m_systemMsgCallback(sm_wndEndResizeMove);

					if(wndSizeChanged)
						m_systemMsgCallback(sm_wndResize);

					if(wndMoved)
						m_systemMsgCallback(sm_wndMoved);
				}
			}
			break;
		case WM_ACTIVATEAPP:
			{
				bool isActive = (wParam == TRUE);
				if(m_systemMsgCallback)
				{
					if(wParam == TRUE)
						m_systemMsgCallback(sm_appActived);
					else
						m_systemMsgCallback(sm_appDeactived);
				}
			}
			break;
		case WM_POWERBROADCAST:
			{
				//
			}
			break;
		case WM_DESTROY:
			PostQuitMessage(0);
			break;
		}
		return DefWindowProc(hWnd, uMsg, wParam, lParam);
	}

	void Win32RenderWindow::SetWindowPosition(const Int2& position)
	{
		if (m_wndPosition == position)
			return;

		SetWindowPos(m_wndHandle, 0, position.x, position.y, m_wndSize.x, m_wndSize.y, 0);
	}

	void Win32RenderWindow::SetClientSize(const Int2& size)
	{
		if(m_wndClientSize == size)
			return;

		int32_t width = (size.x > m_minClientWndSize.x) ? size.x : m_minClientWndSize.x;
		int32_t height = (size.y > m_minClientWndSize.y) ? size.y : m_minClientWndSize.y;
		
		RECT wndSize;
		wndSize.left = m_wndPosition.x;
		wndSize.right = m_wndPosition.x + width;
		wndSize.top = m_wndPosition.y;
		wndSize.bottom = m_wndPosition.y + height;
		AdjustWindowRect(&wndSize, GetWin32WndStyle(), false);
		SetWindowPos(m_wndHandle, 0, wndSize.left, wndSize.top,
			wndSize.right - wndSize.left, wndSize.bottom - wndSize.top, 0);
	}

	void Win32RenderWindow::SetMinClientSize(const Int2& size)
	{
		m_minClientWndSize = size;
		if(m_wndClientSize.x > m_minClientWndSize.x
			|| m_wndClientSize.y > m_minClientWndSize.y)
		{
			SetClientSize(size);
		}
	}
	
	const Int2 Win32RenderWindow::GetMonitorResolution() const
	{
		int width = GetSystemMetrics(SM_CXSCREEN);
		int height = GetSystemMetrics(SM_CYSCREEN);
		return Int2(width, height);
	}

	void Win32RenderWindow::SetTitle(const std::string& title)
	{
		m_title = title;
		SetWindowText(m_wndHandle, m_title.c_str());
	}
	
	void Win32RenderWindow::SetWindowState(WindowState ws)
	{
		assert(false);
	}

	void Win32RenderWindow::SetAllowUserResize(bool enable)
	{
		if(m_allowUserResize != enable)
		{
			m_allowUserResize = enable;
			uint32_t wndStyle = GetWin32WndStyle();
			SetWindowLong(m_wndHandle, GWL_STYLE, wndStyle);
		}
	}
	
	uint32_t Win32RenderWindow::GetWin32WndStyle()
	{
		uint32_t wndStyle = 0;
		if(m_wndStyle == wsf_default)
		{
			wndStyle = WS_CAPTION|WS_MINIMIZEBOX|WS_MAXIMIZEBOX|WS_SYSMENU;	
		}
		else
		{
			if((m_wndStyle & wsf_showTitlebar) > 0)
			{
				wndStyle |= WS_CAPTION;
				wndStyle |= WS_SYSMENU;
			}

			if((m_wndStyle & wsf_showMinimizeBox) > 0)
				wndStyle |= WS_MINIMIZEBOX;

			if((m_wndStyle & wsf_showMaxmizeBox) > 0)
				wndStyle |= WS_MAXIMIZEBOX;
		}

		if(m_allowUserResize)
		{
			wndStyle |= WS_THICKFRAME; 
		}
		else
		{
			wndStyle &= ~WS_THICKFRAME;
			wndStyle &= ~WS_MAXIMIZEBOX;
		}
		
		return wndStyle;
	}

	void Win32RenderWindow::SetInputActionCallback(InputActionCallback& pCallback)
	{
		m_inputCallback = pCallback;
	}

	void Win32RenderWindow::SetSystemMessageCallback(SystemMessageCallback& pCallback)
	{
		m_systemMsgCallback = pCallback;
	}

	void Win32RenderWindow::SetOnHostIdleCallback(HostIdleCallback& pCallback)
	{
		m_HostIdelCallback = pCallback;
	}

	void Win32RenderWindow::ProcessKeyboardInput(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
	{

	}

	void Win32RenderWindow::ProcessMouseInput(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
	{

	}
}